import java.text.DecimalFormat;
import java.util.Arrays;

import Recommendation.Euclidean;
import Recommendation.Pearson;
import User.Rating;
import User.User;

public class Utils
{
	private static User[] users = new User[0];
	private static int[] items = new int[0];
	
	private static double[][] euclidean = new double[0][0];
	
	public static User getUser(int id)
	{
		User u = new User(id);
		int i = Arrays.binarySearch(users, u);
		if (i >= 0)
		{
			return users[i];
		}
		
		User[] newusers = new User[users.length + 1];
		i = (i * (-1)) - 1;
		System.arraycopy(users, 0, newusers, 0, i);
		newusers[i] = u;
		System.arraycopy(users, i, newusers, i + 1, users.length - i);
		users = newusers;
		
		return u;
	}
	
	public static int addItem(int item)
	{
		int i = Arrays.binarySearch(items, item);
		if (i >= 0)
		{
			return i;
		}
		
		int[] newitems = new int[items.length + 1];
		i = (i * (-1)) - 1;
		System.arraycopy(items, 0, newitems, 0, i);
		newitems[i] = item;
		System.arraycopy(items, i, newitems, i + 1, items.length - i);
		items = newitems;
		
		double[][] newEuclidean = new double[euclidean.length + 1][euclidean.length + 1];
		
		for (int x = 0; x < i; x++)
		{
			System.arraycopy(euclidean[x], 0, newEuclidean[x], 0, x);
		}
		
		for (int x = i; x < euclidean.length; x++)
		{
			System.arraycopy(euclidean[x], 0, newEuclidean[x + 1], 0, i);
			System.arraycopy(euclidean[x], i, newEuclidean[x + 1], i + 1, x - i);
		}
		
		euclidean = new double[newEuclidean.length][newEuclidean.length];
		System.arraycopy(newEuclidean, 0, euclidean, 0, newEuclidean.length);

		return i;
	}
	
	public static void addRating(int userid, int id, double rating)
	{
		User u = getUser(userid);
		int index = addItem(id);
		u.addRating(id, rating);
		
		for (int i = 0; i < index; i++)
		{
			if (u.getRating(id) != -1 && u.getRating(items[i]) != -1)
			{
				euclidean[index][Arrays.binarySearch(items, items[i])] = new Euclidean(new double[]{u.getRating(id)}, new double[]{u.getRating(items[i])}).extend(euclidean[index][Arrays.binarySearch(items, items[i])]);
			}
		}
		
		for (int i = index; i < items.length; i++)
		{
			if (u.getRating(id) != -1 && u.getRating(items[i]) != -1)
			{
				euclidean[Arrays.binarySearch(items, items[i])][index] = new Euclidean(new double[]{u.getRating(id)}, new double[]{u.getRating(items[i])}).extend(euclidean[Arrays.binarySearch(items, items[i])][index]);
			}
		}
	}
	
	public static Rating[] getAdvice(User selectedUser)
	{
		User bestMatch = bestMatch(selectedUser, users);
		if (bestMatch != null)
		{
			int[] advice = selectedUser.getNotShared(bestMatch.getItemids());
			
			Rating[] result = new Rating[0];
			for (int id : advice)
			{
				Rating[] temp = new Rating[result.length + 1];
				System.arraycopy(result, 0, temp, 0, result.length);
				temp[result.length] = new Rating(id, bestMatch.getRating(id) * new Pearson(selectedUser, bestMatch).calc());
				
				result = new Rating[temp.length];
				System.arraycopy(temp, 0, result, 0, temp.length);
			}
			
			Arrays.sort(result, Rating.RatingComparator);
			
			return result;
		}
		
		return null;
	}
	
	public static Rating[] getSlopeAdvice(User u)
	{
		int[] not = new int[items.length - u.getItemids().length];
		
		int found = 0;
		for (int i : items)
		{
			if (Arrays.binarySearch(u.getItemids(), i) < 0)
			{
				not[found] = i;
				found++;
			}
		}
		
		Rating[] result = new Rating[not.length];
		for (int i = 0; i < not.length; i++)
		{
			int index = Arrays.binarySearch(items, not[i]);
			double f = 0, p = 0;
			
			for (int id : u.getItemids())
			{
				int index2 = Arrays.binarySearch(items, id);
				
				double value = euclidean[index][index2];
				if (value == 0 && !Double.isNaN(euclidean[index2][index]))
				{
					value = euclidean[index2][index];
				}
				
				if (!Double.isNaN(value) && value != 1.0)
				{
					f += value;
					p += value * u.getRating(id);
				}
			}
			
			Rating r = new Rating(not[i], p / f);
			
			if (Double.isNaN(r.getRating()))
			{
				r.setRating(0);
			}
			
			result[i] = r;
		}
		
		Arrays.sort(result, Rating.RatingComparator);
		
		return result;
	}
	
	private static User bestMatch(User user, User[] users)
	{
		User bestMatch = null;
		double best = 0;
		
		for (int i = 0;  i < users.length; i++)
		{
			if (!user.equals(users[i]))
			{
				double pearson = new Pearson(user, users[i]).calc();
				if (pearson > best)
				{
					bestMatch = users[i];
					best = pearson;
				}
			}
		}
		
		return bestMatch;
	}
	
	public static void printEuclidean()
	{
		System.out.print("\t | \t");
		for (int id : items)
		{
			System.out.print(id + "\t");
		}
		System.out.println();
		System.out.println("----------------------------------------------------------------------");
		
		DecimalFormat df = new DecimalFormat("#,###,##0.000");
		
		for (int i = 0; i < euclidean.length; i++)
		{
			System.out.print(items[i] + "\t | \t");
			
			for (int j = 0; j < euclidean[i].length; j++)
			{
				if (euclidean[i][j] != 0 && euclidean[i][j] != 1)
				{
					System.out.print(df.format(euclidean[i][j]) + "\t");
				}
				else if (euclidean[i][j] == 1)
				{
					System.out.print("?\t");
				}
			}
			System.out.println();
		}
	}
}
